#
# openlookup/server/xmlrpc.py
#
# Copyright 2008-2009 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Ken Rimey <rimey@hiit.fi>
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import logging
from binascii import hexlify, unhexlify
from xmlrpclib import Binary

from openlookup.lib.stateless import Stateless
from openlookup.engine.multivalued import MultivaluedEngine

METHODS = ['get', 'get_details', 'put', 'put_removable', 'rm']

class XMLRPCEngine(object):
    """
    XML-RPC interface compatible with OpenDHT (http://opendht.org/)
    """
    def __init__(self, filename, fast):
        self.engine = Stateless(MultivaluedEngine, filename, fast=fast)
        self.stats = {}        # calls by application and method

    def handle_get(self, key, maxvals, placemark, application):
        self.log('get', application)

        key = key.data
        placemark = hexlify(placemark.data)
        results, placemark = self.engine.query(key, maxvals, placemark)

        values = [Binary(x[0]) for x in results]
        placemark = Binary(unhexlify(placemark))
        return [values, placemark]

    def handle_get_details(self, key, maxvals, placemark, application):
        self.log('get_details', application)

        key = key.data
        placemark = hexlify(placemark.data)
        results, placemark = self.engine.query(key, maxvals, placemark)

        def process_results():
            for value, ttl, secret_hash in results:
                yield [Binary(value),
                       ttl,
                       ('SHA' if secret_hash else ''),
                       Binary(unhexlify(secret_hash))]

        results = list(process_results())
        placemark = Binary(unhexlify(placemark))
        return [results, placemark]

    def handle_put(self, key, value, ttl, application):
        self.log('put', application)
        self.engine.put(key.data, value.data, ttl)
        return 0                # 1 for "over capacity"

    def handle_put_removable(self, key, value,
                             hash_type, secret_hash, ttl, application):
        self.log('put_removable', application)

        if hash_type not in ['', 'SHA']:
            raise ValueError('Unrecognized hash type.')

        key = key.data
        value = value.data
        secret_hash = hexlify(secret_hash.data)
        self.engine.put(key, value, ttl, secret_hash)
        return 0                # 1 for "over capacity"

    def handle_rm(self, key, value_hash, hash_type, secret, ttl, application):
        self.log('rm', application)

        if hash_type != 'SHA':
            raise ValueError('Unrecognized hash type.')

        key = key.data
        secret = secret.data
        value_hash = hexlify(value_hash.data)
        self.engine.rm(key, value_hash, secret, ttl)
        return 0                # 1 for "over capacity"

    def log(self, method, application):
        logging.debug('Received %s request from %s.',
                      method.upper(), application)
        if isinstance(application, (str, unicode)):
            calls_by_method = self.stats.setdefault(application, {})
            if not calls_by_method:
                logging.info('New application: %s', application)
            calls_by_method[method] = calls_by_method.get(method, 0) + 1
